package com.wili.c;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.CodeSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.io.FileUtils;
import com.c.Ca;
/**
 * 
 * @author weila 2020年3月1日
 */
public class File9 {
	public static final String DISK = "D:/";

	public static <T> T readObject2(File fromFile, int throwErr) {
		T ret = readObject(fromFile, null, throwErr == 1 ? "error" : "");
		if (ret == null && throwErr == 1)
			throw C2.newRuntimeException(new FileNotFoundException(fromFile.getAbsolutePath()));
		return ret;
	}

	public static <T> T readObject(File fromFile) {
		return readObject(fromFile, null, "");
	}

	public static <T> T readObject(File fromFile, T def, String errMsg) {
		/*
		 * lastModified: if return File, it's lastModified will be refreshed
		 * immediately.
		 */
		try {
			// C2.log(1, "File9.readObject:", fromFile);
			if (!fromFile.exists())
				return def;
			synchronized (LOCK) {
				if ("sd".isEmpty())
					System.out.println("File9.readObject:" + fromFile);
				return readObject(new FileInputStream(fromFile));
			}
		} catch (Exception e) {
			if (errMsg.isEmpty())
				return def;
			new Exception(errMsg + ":" + e.toString()).printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public static boolean writeObject(Object obj, File toFile, int print) {
		try {
			if (print == 1)
				System.out.print(" ~writeObject: " + toFile.getAbsolutePath() + ",...");
			boolean ret = writeObject2(obj, toFile);
			if (print == 1)
				System.out.println(ret);
			return ret;
		} catch (NotSerializableException e) {
			e.printStackTrace();
			return false;
		}
	}

	public static boolean writeObject2(Object obj, File toFile) throws NotSerializableException {
		ObjectOutputStream os = null;
		try {
			if (!toFile.exists()) {
				// System.out.println(" save toFile=" +
				// toFile.getAbsolutePath());
				toFile.getParentFile().mkdirs();
				toFile.createNewFile();
			}
			synchronized (LOCK) {
				os = new ObjectOutputStream(new FileOutputStream(toFile));
				return writeObject(obj, os);
			}
		} catch (NotSerializableException e) {
			throw e;
		} catch (IOException e) {
			C2.asert(!toFile.exists());
			e.printStackTrace();
			return false;
		} finally {
			try {
				if (os != null)
					os.close();
			} catch (IOException e) {
				C2.asert(!toFile.exists());
				throw new RuntimeException(e);
			}
		}
	}
	static final Integer LOCK = Integer.valueOf(1);
	public static boolean writeObject(Object obj, ObjectOutputStream os) {
		try {
			if ("77".isEmpty()) {
				BufferedOutputStream buf = new BufferedOutputStream(os, 10 * 1024 * 1024);
				byte[] bb = new byte[10 * 1024 * 1024];
				buf.write(bb, 0, bb.length);
				buf.flush();
				buf.close();
			} else {
				os.writeObject(obj);
			}
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				os.close();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * @param in
	 * @return ret. ClassNotFoundException if ret.getClass() not in classpath.<br>
	 *         ret.getClass() and it's super class and interface must be in
	 *         classpath too.<br>
	 *         static fields will be the initial value of class.
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 */
	@SuppressWarnings("unchecked")
	public static <T> T readObject(InputStream in) throws IOException, ClassNotFoundException {
		ObjectInputStream os = new ObjectInputStream(in);
		T ret = (T) os.readObject();
		os.close();
		return ret;
	}

	/**
	 * 
	 * @param s0
	 * @param tofile
	 * @param charset e.g. utf-8 | gb2312 | ...
	 * @return
	 */
	public static boolean str2file(String s0, File tofile, String charset, boolean append) {
		if (!Boolean.TRUE) {
			try {//StringInputStream is a error?
				FileUtils.copyInputStreamToFile(new ByteArrayInputStream(s0.getBytes()), tofile);
				return true;
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}
		OutputStreamWriter wt = null;
		if (!tofile.getParentFile().exists()) {
			tofile.getParentFile().mkdirs();
		}
		if (tofile.getName().equals(" SolveVarAct.arff"))
			C2.asert(false, "todo:" + tofile);// 09-26
		try {
			if (tofile.getName().contains("PartRelation_trans")) {
				C2.pause();
			}
			if (charset == null || charset.isEmpty()) {
				wt = new OutputStreamWriter(new FileOutputStream(tofile, append));
			} else {
				wt = new OutputStreamWriter(new FileOutputStream(tofile, append), charset);
			}
			BufferedWriter bw = new BufferedWriter(wt);
			bw.write(s0);
			bw.flush();
			// bw.close();?
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			if (wt != null) {
				try {
					wt.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static final AppendLinesThread appendLineThread = new AppendLinesThread();

	static final AtomicLong appendLineTimes = new AtomicLong(0);
	public static void appendLine(String line, File toFile, boolean useBuff) {
		if (!useBuff) {
			appendLine00(line, toFile);
			return;
		}
		long curTime = System.currentTimeMillis();
		synchronized (toFile) {
			FileDataInfo info = appendLineThread.map.get(toFile);
			if (info == null) {
				info = new FileDataInfo();
				appendLineThread.map.put(toFile, info);
			}
			boolean is = appendLineTimes.incrementAndGet() % 10 == 0 || curTime - info.preTime > 1000
					|| info.preTime == -1;
			is = curTime - info.preTime > 10000;
			if (is) {
				info.preTime = curTime;
				info.append(NL + Date9.dateTimeStrOfNow());
			}
			info.lineId++;
			// info.append(NL + info.lineId + ":");
			info.append(NL + line);
		}
		if (!appendLineThread.isAlive()) {
			appendLineThread.start();
		}
	}
	static class FileDataInfo {
		long preTime = -1;

		int lineId = 0;

		StringBuilder sb = new StringBuilder();
		public void append(String s0) {
			sb.append(s0);
		}
	}
	public static class AppendLinesThread extends Thread {
		final Map<File, FileDataInfo> map = new ConcurrentHashMap<File, FileDataInfo>();
	}
	public static boolean appendLine00(String line, File tofile) {
		try {
			// System.out.println("File9.appendLine:" + line);
			BufferedWriter bw = new BufferedWriter(new FileWriter(tofile, true));
			bw.newLine();
			bw.write(line);
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public static boolean isAncestorDir(File anDir, File file) {
		String anPath = anDir.getAbsolutePath();
		String fPath = file.getAbsolutePath();
		//eg. c:/abc/d.txt startswith c:/ab, but not offspring.
		if (fPath.startsWith(anPath))
			return fPath.charAt(anPath.length()) == File.separatorChar;
		return false;
	}

	public static String strOfFile(File file) {
		return strOfFile(file, null, "\n");
	}

	public static String strOfFile(File file, String charset, String NL) {
		try {
			if ("".isEmpty()) {
				return FileUtils.readFileToString(file, Charset.defaultCharset());
			}
			if (file.length() > Integer.MAX_VALUE)
				throw new RuntimeException("file length too long:" + file.getAbsolutePath());
			StringBuilder sb = new StringBuilder((int) (file.length() * 1.1));
			String line;
			InputStreamReader ir;
			if (charset == null) {
				ir = new InputStreamReader(new FileInputStream(file));
			} else {
				ir = new InputStreamReader(new FileInputStream(file), charset);
			}
			BufferedReader br = new BufferedReader(ir);
			int ind = 0;
			String last = null;
			while ((line = br.readLine()) != null) {
				if (++ind > 1)
					sb.append(NL);
				sb.append(line);
				last = line;
			}
			//if last char is '\n', will be ignored, how to do this?
			br.close();
			return sb.toString();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	static final String NL = System.getProperty("line.separator");
	/**
	 * 当文件系统有文件增减时，此方法失效, 当改用 listFiles(dir,filter);
	 * 
	 * @param dir
	 * @param filt
	 * @return
	 */
	public static interface TranverseFun {
		void doFun(int depth, File file);
	}
	public static <T> void traverseFile(File file, int depth, TranverseFun fun) {
		++depth;
		if (file.isDirectory()) {
			File[] ff = file.listFiles();
			for (File file2 : ff) {
				traverseFile(file2, depth, fun);
			}
		} else {
			fun.doFun(depth, file);
		}
	}

	/**
	 * if $ancestor is c:/aa, $cur is c:/aa/x/y.txt, the relative-path is
	 * '/x/y.txt'.
	 */
	public static String relativePathOf(File ancestorDir, File file) {
		if (!isAncestorDir(ancestorDir, file))
			return null;
		String an, cu;
		an = ancestorDir.getPath();
		cu = file.getPath();
		if (!cu.startsWith(an))
			return null;
		return cu.substring(an.length());
	}

	public static File javafileOf(int[] retInd, String path, File... sourceDirs) {
		for (int i = 0; i < sourceDirs.length; i++) {
			File dir = sourceDirs[i];
			if (dir.getName().equals("src_core"))
				Ca.pause();
			File ret = new File(dir, path);
			if (ret.exists()) {
				return ret;
			}
		}
		return null;
	}

	public static File javafileOf(int[] retInd, Class<?> javaClazz, File... sourceDirs) {
		if (javaClazz.isPrimitive())
			return null;
		if (javaClazz.isMemberClass())//否则可能找到不匹配的文件
			return null;
		String path = javaClazz.getPackage().getName().replace('.', '/') + '/' + javaClazz.getSimpleName() + ".java";
		return javafileOf(retInd, path, sourceDirs);
	}
	/**
	 * 未成功,原因是 RandAccessFile.getFilePoint(),file.length() 和字符下标不是一回事儿. <br>
	 * 从大日志中查找给定的日期所在的第一个下标(因日志有序,使用二分法查找)
	 * 
	 * @param date
	 * @param file
	 * @return
	 * @throws IOException
	 */

	public static File clsFileOfClass(Class<?> cls, File clsRoot) {
		if (clsRoot == null) {
			CodeSource src = cls.getProtectionDomain().getCodeSource();
			if (src != null) {
				try {
					clsRoot = new File(src.getLocation().toURI());
				} catch (URISyntaxException e) {
					e.printStackTrace();
					return null;
				}
			}
		}
		String cname = cls.getName();//wili.c.asm.Demo110$1
		return new File(clsRoot, cname.replace('.', '/') + ".class");
	}
}